export interface PistonRuntime {
  language: string;
  version: string;
  aliases: string[];
  runtime?: string;
}

export interface PistonFile {
  name: string;
  content: string;
}

export interface PistonExecuteRequest {
  language: string;
  version: string;
  files: PistonFile[];
  stdin?: string;
  args?: string[];
  compile_timeout?: number; // milliseconds
  run_timeout?: number;     // milliseconds
  compile_memory_limit?: number; // bytes
  run_memory_limit?: number;     // bytes
}

interface PistonOutput {
  stdout: string;
  stderr: string;
  output: string;
  code: number;
  signal: string | null;
}

export interface PistonExecuteResponse {
  language: string;
  version: string;
  run: PistonOutput;
  compile?: PistonOutput; // Present for compiled languages
}

const PISTON_API_URL = process.env.PISTON_API_URL || 'https://emkc.org/api/v2/piston';

/**
 * Fetches the available runtimes (languages and versions) from the Piston API.
 */
export async function getPistonRuntimes(): Promise<PistonRuntime[]> {
  try {
    const response = await fetch(`${PISTON_API_URL}/runtimes`);
    if (!response.ok) {
      const errorData = await response.json().catch(() => ({ message: response.statusText }));
      console.error('Piston getRuntimes failed:', response.status, errorData);
      throw new Error(`Piston getRuntimes failed: ${errorData.message || response.statusText}`);
    }
    return await response.json() as PistonRuntime[];
  } catch (error) {
    console.error('Error fetching Piston runtimes:', error);
    throw error;
  }
}

/**
 * Executes code using the Piston API.
 * @param payload - The execution request payload.
 * @returns A promise that resolves to the execution result.
 */
export async function executeCodeInPiston(payload: PistonExecuteRequest): Promise<PistonExecuteResponse> {
  try {
    const response = await fetch(`${PISTON_API_URL}/execute`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      // Piston API might return detailed errors in the run/compile stderr for non-200s sometimes
      const errorData = await response.json().catch(() => ({ message: response.statusText }));
      console.error('Piston execute request failed:', response.status, errorData);
      // Construct a more informative error if possible
      let errorMessage = `Piston execute request failed: ${errorData.message || response.statusText}`;
      if (errorData.message && typeof errorData.message === 'string') {
         errorMessage = errorData.message; // Piston often puts the error directly in 'message'
      } else if (errorData.run?.stderr) {
         errorMessage = `Piston execution error: ${errorData.run.stderr}`;
      } else if (errorData.compile?.stderr) {
         errorMessage = `Piston compilation error: ${errorData.compile.stderr}`;
      }
      throw new Error(errorMessage);
    }
    return await response.json() as PistonExecuteResponse;
  } catch (error) {
    console.error('Error executing code with Piston:', error);
    throw error;
  }
}

// --- Helper functions to map Piston results to your application's format ---
// (Similar to what we had for Judge0, but adapted for Piston's response structure)

export type SimplifiedPistonStatus = 
  | "ACCEPTED" 
  | "WRONG_ANSWER" 
  | "TIME_LIMIT_EXCEEDED" // Piston might report TLE via stderr or non-zero exit code
  | "COMPILATION_ERROR"
  | "RUNTIME_ERROR" 
  | "MEMORY_LIMIT_EXCEEDED" // Piston might report MLE via stderr or non-zero exit code
  | "INTERNAL_ERROR"
  | "PENDING"; // Not directly applicable as Piston returns result synchronously

export interface MappedPistonResult {
  status: SimplifiedPistonStatus;
  isCorrect: boolean;
  pointsAwarded: number;
  actualOutput: string | null;
  runtimeError: string | null; // Combined compile and runtime errors
  executionTimeMs?: number; // Piston doesn't directly provide this easily, might need to infer or omit
  memoryUsageKb?: number;   // Piston doesn't directly provide this easily
}

/**
 * Maps Piston execute response to a simplified result structure.
 * @param pistonResponse - The response from Piston API.
 * @param expectedOutput - The expected output for comparison.
 * @param pointsForTestCase - Points for this test case if correct.
 */
export function mapPistonResultToSimplified( 
  pistonResponse: PistonExecuteResponse, 
  expectedOutput?: string | null,
  pointsForTestCase: number = 0 
): MappedPistonResult {
  let status: SimplifiedPistonStatus = "INTERNAL_ERROR";
  let isCorrect = false;
  let pointsAwarded = 0;
  let actualOutput = pistonResponse.run?.stdout || null;
  let runtimeError = pistonResponse.run?.stderr || null;

  if (pistonResponse.compile?.stderr) {
    runtimeError = `Compilation Failed: ${pistonResponse.compile.stderr}${runtimeError ? `\nRuntime: ${runtimeError}` : ''}`;
    status = "COMPILATION_ERROR";
  }

  if (status !== "COMPILATION_ERROR") {
    if (pistonResponse.run?.code === 0) { // Successful execution (exit code 0)
      if (expectedOutput !== undefined && expectedOutput !== null) {
        const expected = String(expectedOutput).replace(/\r\n/g, '\n').trim();
        const actual = String(actualOutput || '').replace(/\r\n/g, '\n').trim();
        if (expected === actual) {
          status = "ACCEPTED";
          isCorrect = true;
          pointsAwarded = pointsForTestCase;
        } else {
          status = "WRONG_ANSWER";
        }
      } else {
        // No expected output, so any successful run is considered "ACCEPTED"
        status = "ACCEPTED";
        isCorrect = true;
        pointsAwarded = pointsForTestCase;
      }
    } else { // Non-zero exit code, or error in stderr
      status = "RUNTIME_ERROR";
      if (pistonResponse.run?.stderr?.toLowerCase().includes('time limit')) {
        status = "TIME_LIMIT_EXCEEDED";
      } else if (pistonResponse.run?.stderr?.toLowerCase().includes('memory limit')) {
        status = "MEMORY_LIMIT_EXCEEDED";
      }
      // runtimeError is already set from pistonResponse.run.stderr
    }
  }
  
  // Piston does not directly return execution time or memory in a structured way per test case run.
  // These would need to be inferred from timeout/memory limit settings or are generally not available.

  return {
    status,
    isCorrect,
    pointsAwarded,
    actualOutput,
    runtimeError,
  };
} 